﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Net.Browser;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Windows.Threading;
using System.Windows.Media.Imaging;
using System.Xml.Serialization;
using System.Globalization;

namespace AzureBlobManager
{
    public class AzureTransfertFactory
    {
        public static IAzureTranfertManager GetTransferManager(Blob blob)
        {
            IAzureTranfertManager manager;
            if (blob.IsUpload)
            {
                manager = new AzureUploadManager();
            }
            else
            {
                manager = new AzureDownloadManager();
            }
            blob.Manager = manager;
            return manager;
        }
    }

    public interface IAzureTranfertManager
    {
        void Initialize(Stream stream, string fileName, Blob blob, Dispatcher dispatcher);

        void StartTransfer();
    }

    public class AzureUploadManager : IAzureTranfertManager
    {
        private Blob _blob;
        //private FileInfo _fileInfo;

        private long _chunkSize = 4194304;
        private Stream _fileStream;
        private string _uploadUrl;

        private bool _useBlocks;
        private long _dataLength;
        private long _dataSent;

        private string _currentBlockId;
        private List<string> _blockIds = new List<string>();

        private Dispatcher _dispatcher;

        public void Initialize(Stream stream, string fileName, Blob blob, Dispatcher dispatcher)
        {
            _blob = blob;

            if (stream.Length > _chunkSize)
            {
                _useBlocks = true;
            }
            else
            {
                _useBlocks = false;
            }

            var uriBuilder = new UriBuilder(blob.SharedAccessSignature);
            uriBuilder.Path += string.Format("/{0}", fileName);
            _uploadUrl = uriBuilder.Uri.AbsoluteUri;

            _blob.Name = fileName;
            _dispatcher = dispatcher;

            _fileStream = stream;
            _blob.FileSize = _dataLength = _fileStream.Length;
            _dataSent = 0;
        }

        public void StartTransfer()
        {
            _dispatcher.BeginInvoke(delegate()
            {
                _blob.InProgress = true;
            });

            long dataToSend = _dataLength - _dataSent;

            var uriBuilder = new UriBuilder(_uploadUrl);

            if (_useBlocks)
            {
                // encode the block name and add it to the query string 
                _currentBlockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()));
                uriBuilder.Query = uriBuilder.Query.TrimStart('?') +
                    string.Format("&comp=block&blockid={0}", _currentBlockId);
            }

            // with or without using blocks, we'll make a PUT request with the data
            HttpWebRequest webRequest = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(uriBuilder.Uri);
            webRequest.Method = "PUT";
            webRequest.BeginGetRequestStream(new AsyncCallback(WriteToStreamCallback), webRequest);
        }

        #region Put Block Async Calls

        // write to ChunkSize of data to the web request
        private void WriteToStreamCallback(IAsyncResult asynchronousResult)
        {
            HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
            Stream requestStream = webRequest.EndGetRequestStream(asynchronousResult);

            byte[] buffer = new Byte[4096];
            int bytesRead = 0;
            int tempTotal = 0;

            _fileStream.Position = _dataSent;

            while ((bytesRead = _fileStream.Read(buffer, 0, buffer.Length)) != 0 && tempTotal + bytesRead < _chunkSize && !_blob.Deleted && _blob.State != FileState.Error)
            {
                requestStream.Write(buffer, 0, bytesRead);
                requestStream.Flush();

                _dataSent += bytesRead;
                tempTotal += bytesRead;

                _dispatcher.BeginInvoke(delegate()
                {
                    OnProgressChanged();
                });
            }
            requestStream.Close();

            webRequest.BeginGetResponse(new AsyncCallback(ReadHttpResponseCallback), webRequest);
        }

        private void ReadHttpResponseCallback(IAsyncResult asynchronousResult)
        {
            bool error = false;

            try
            {
                HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
                HttpWebResponse webResponse = (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult);
                StreamReader reader = new StreamReader(webResponse.GetResponseStream());

                string responsestring = reader.ReadToEnd();
                reader.Close();
            }
            catch
            {
                error = true;

                _dispatcher.BeginInvoke(delegate()
                {
                    _blob.State = FileState.Error;
                });
            }

            if (!error)
            {
                _blockIds.Add(_currentBlockId);
            }

            // if there's more data, send another request
            if (_dataSent < _dataLength)
            {
                if (_blob.State != FileState.Error && _blob.State != FileState.Cancel && !error)
                {
                    StartTransfer();
                }
            }
            else // all done
            {
                _fileStream.Close();
                _fileStream.Dispose();

                if (_useBlocks)
                {
                    // commit the blocks into the blob.
                    PutBlockList();
                }
                else
                {
                    UpdateFieldValueToMatchContainer();
                    _dispatcher.BeginInvoke(delegate()
                    {
                        new BitmapImage(new Uri("Images/done.png", UriKind.Relative));
                        _blob.InProgress = false;
                    });
                }
            }
        }

        #endregion

        #region Put Block list Async Calls

        private void PutBlockList()
        {
            HttpWebRequest webRequest = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(
                new Uri(string.Format("{0}&comp=blocklist", _uploadUrl)));
            webRequest.Method = "PUT";
            webRequest.Headers["x-ms-version"] = "2009-09-19"; // x-ms-version is required for put block list!
            webRequest.BeginGetRequestStream(new AsyncCallback(BlockListWriteToStreamCallback), webRequest);
        }

        private void BlockListWriteToStreamCallback(IAsyncResult asynchronousResult)
        {
            HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
            Stream requestStream = webRequest.EndGetRequestStream(asynchronousResult);

            var document = new XDocument(
                new XElement("BlockList",
                    from blockId in _blockIds
                    select new XElement("Uncommitted", blockId)));
            var writer = XmlWriter.Create(requestStream, new XmlWriterSettings() { Encoding = Encoding.UTF8 });
            document.Save(writer);
            writer.Flush();

            requestStream.Close();

            webRequest.BeginGetResponse(new AsyncCallback(BlockListReadHttpResponseCallback), webRequest);
        }

        private void BlockListReadHttpResponseCallback(IAsyncResult asynchronousResult)
        {
            bool error = false;

            try
            {
                HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
                HttpWebResponse webResponse = (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult);
                StreamReader reader = new StreamReader(webResponse.GetResponseStream());

                string responsestring = reader.ReadToEnd();
                reader.Close();
            }
            catch
            {
                error = true;

                _dispatcher.BeginInvoke(delegate()
                {
                    _blob.StatusImage = new BitmapImage(new Uri("Images/cancel.png", UriKind.Relative));
                });
            }

            if (!error)
            {
                UpdateFieldValueToMatchContainer();
                _dispatcher.BeginInvoke(delegate()
                {
                    _blob.StatusImage = new BitmapImage(new Uri("Images/done.png", UriKind.Relative));
                });
            }
        }

        #endregion

        #region Updating field In SP

        public void UpdateFieldValueToMatchContainer()
        {
            WebClient client = new WebClient();
            client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(UpdateFieldValueToMatchContainer_Completed);
            client.DownloadStringAsync(new Uri(InitParams.CurrentWebURI +
                String.Format("/_vti_bin/AzureBlobServices/AzureBlobService.svc/ValidateUpload?guid={0}&name={1}&temp=",
                _blob.Guid, _blob.Name, Guid.NewGuid())));
        }

        public void UpdateFieldValueToMatchContainer_Completed(object sender, DownloadStringCompletedEventArgs e)
        {           
        }

        #endregion

        private void OnProgressChanged()
        {
            _blob.BytesUploaded = _dataSent;
        }
    }

    public class AzureDownloadManager : IAzureTranfertManager
    {
        private Blob _blob;

        private Stream _fileStream;
        private string _downloadUrl;

        private bool _useBlocks;
        private long _dataLength;

        private long _currentPosition = 0;
        private int _currentBlock;
        private int _totalBlocks;
        private List<Block> _blockIds = new List<Block>();

        private Dispatcher _dispatcher;

        public void Initialize(Stream stream, string fileName, Blob blob, Dispatcher dispatcher)
        {
            _blob = blob;
            _fileStream = stream;

            // TODO: Adding file name, dont know if really necessary...
            var uriBuilder = new UriBuilder(blob.SharedAccessSignature);
            uriBuilder.Path += string.Format("/{0}", blob.Name);
            _downloadUrl = uriBuilder.Uri.AbsoluteUri;

            _blob.Name = fileName;
            _dispatcher = dispatcher;
        }

        public void StartTransfer()
        {
            _dispatcher.BeginInvoke(delegate()
            {
                _blob.InProgress = true;
            });

            HttpWebRequest webRequest;
            webRequest = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(
            new Uri(string.Format("{0}&comp=blocklist&blocklisttype=committed", _downloadUrl)));
            webRequest.Method = "GET";
            webRequest.Headers["x-ms-version"] = "2009-09-19"; // x-ms-version is required for put block list!
            webRequest.BeginGetResponse(new AsyncCallback(BlockListReadHttpResponseCallback), webRequest);
        }

        private void GetNextBlock()
        {
            if (_currentBlock <= (_totalBlocks - 1))
            {
                HttpWebRequest webRequest = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(new Uri(_downloadUrl));

                long offset = (_currentPosition == 0) ? 0 : 1;

                webRequest.Method = "GET";
                webRequest.Headers["x-ms-version"] = "2009-09-19";
                webRequest.Headers["x-ms-range"] = String.Format(CultureInfo.InvariantCulture, "bytes={0}-{1}",
                    _currentPosition + offset, (_currentPosition + _blockIds[_currentBlock].Size).ToString()); // Range of data to get.
                webRequest.BeginGetResponse(new AsyncCallback(BLobContentReadHttpResponseCallback), webRequest);

                _currentPosition += _blockIds[_currentBlock].Size;
            }
            else
            {
                // all done !
                _fileStream.Flush();

                _dispatcher.BeginInvoke(delegate()
                {
                    _fileStream.Close();
                    _blob.StatusImage = new BitmapImage(new Uri("Images/done.png", UriKind.Relative));
                    _blob.InProgress = false;
                }); 
            } 
        }

        #region Get Block list Async Calls

        private void BlockListReadHttpResponseCallback(IAsyncResult asynchronousResult)
        {
            bool error = false;
            string responseString = String.Empty;

            try
            {
                HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
                HttpWebResponse webResponse = (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult);
                StreamReader reader = new StreamReader(webResponse.GetResponseStream());

                responseString = reader.ReadToEnd();
                reader.Close();
            }
            catch
            {
                error = true;

                _dispatcher.BeginInvoke(delegate()
                {
                    _blob.StatusImage = new BitmapImage(new Uri("Images/cancel.png", UriKind.Relative));
                    _blob.InProgress = false;
                });
            }

            if (!error)
            {
                // Received the blocks, now need to get all of them to write to the Stream.
                byte[] bytes = System.Text.Encoding.UTF8.GetBytes(responseString);
                MemoryStream stream = new MemoryStream(bytes);

                XmlSerializer serializer = new XmlSerializer(typeof(BlockList));
                BlockList blocks = serializer.Deserialize(stream) as BlockList;
                stream.Close();

                // Checking if blob as blocks or not.
                if (blocks.CommittedBlocks.Count > 0)
                {
                    _useBlocks = true;

                    _blockIds = blocks.CommittedBlocks;
                    _blob.FileSize = _blockIds.Sum(blob => blob.Size);
                    _currentBlock = 0;
                    _totalBlocks = _blockIds.Count;

                    // Initiating block download.
                    GetNextBlock();
                }
                else
                {
                    _useBlocks = false;
                    HttpWebRequest webRequest = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(new Uri(_downloadUrl));
                    webRequest.Method = "GET";
                    webRequest.Headers["x-ms-version"] = "2009-09-19";
                    webRequest.BeginGetResponse(new AsyncCallback(OneTimeReadHttpResponseCallback), webRequest);
                }
            }
        }

        #endregion

        #region Get BLob BLocks Async Calls

        private void BLobContentReadHttpResponseCallback(IAsyncResult asynchronousResult)
        {
            bool error = false;
            string responseString = String.Empty;

            byte[] result = null;
            byte[] buffer = new byte[4096];

            try
            {
                HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
                HttpWebResponse webResponse = (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult);

                // Work arround for reading raw bytres out of stream.
                using (Stream responseStream = webResponse.GetResponseStream())
                {
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        int count = 0;
                        do
                        {
                            count = responseStream.Read(buffer, 0, buffer.Length);
                            memoryStream.Write(buffer, 0, count);

                        } while (count != 0);

                        result = memoryStream.ToArray();
                    }
                }
            }
            catch
            {
                error = true;

                _dispatcher.BeginInvoke(delegate()
                {
                    _blob.StatusImage = new BitmapImage(new Uri("Images/cancel.png", UriKind.Relative));
                });
            }

            if (!error)
            {
                // Received blob block, writing to disk.
                _fileStream.Write(result, 0, result.Length);
                _fileStream.Flush();

                _dataLength += result.Length;
                _dispatcher.BeginInvoke(delegate()
                {
                    OnProgressChanged();
                });

                _currentBlock++;
                GetNextBlock();
            }
        }

        #endregion

        #region Get Blob In one piece Async Call

        private void OneTimeReadHttpResponseCallback(IAsyncResult asynchronousResult)
        {
            bool error = false;
            string responseString = String.Empty;

            byte[] result = null;
            byte[] buffer = new byte[4096];

            try
            {
                HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
                HttpWebResponse webResponse = (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult);

                // Work arround for reading raw bytres out of stream.
                using (Stream responseStream = webResponse.GetResponseStream())
                {
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        int count = 0;
                        do
                        {
                            count = responseStream.Read(buffer, 0, buffer.Length);
                            memoryStream.Write(buffer, 0, count);

                        } while (count != 0);

                        result = memoryStream.ToArray();
                    }
                }
            }
            catch
            {
                error = true;

                _dispatcher.BeginInvoke(delegate()
                {
                    _blob.StatusImage = new BitmapImage(new Uri("Images/cancel.png", UriKind.Relative));
                });
            }

            if (!error)
            {
                // Received blob, writing to disk.
                _fileStream.Write(result, 0, result.Length);
                _fileStream.Flush();

                // Polishing the interface :)
                _dispatcher.BeginInvoke(delegate()
                {
                    _blob.BytesUploaded = result.Length;
                    _fileStream.Close();
                    _blob.StatusImage = new BitmapImage(new Uri("Images/done.png", UriKind.Relative));
                    _blob.InProgress = false;
                }); 
            }
        }

        #endregion

        private void OnProgressChanged()
        {
            _blob.BytesUploaded = _dataLength;
        }
    }
}
